import { Cartesian2 } from '../../Source/Cesium.js';
import { Cartesian3 } from '../../Source/Cesium.js';
import { defineProperties } from '../../Source/Cesium.js';
import { Ellipsoid } from '../../Source/Cesium.js';
import { Event } from '../../Source/Cesium.js';
import { JulianDate } from '../../Source/Cesium.js';
import { CustomDataSource } from '../../Source/Cesium.js';
import { DataSourceDisplay } from '../../Source/Cesium.js';
import { Entity } from '../../Source/Cesium.js';
import { EntityCluster } from '../../Source/Cesium.js';
import { SceneTransforms } from '../../Source/Cesium.js';
import createCanvas from '../createCanvas.js';
import createScene from '../createScene.js';

describe('DataSources/EntityCluster', function() {

    var scene;
    var cluster;
    var depth;
    var farDepth;

    beforeAll(function() {
        scene = createScene({
            canvas : createCanvas(10, 10)
        });
        scene.globe = {
            ellipsoid : Ellipsoid.WGS84,
            _surface : {
                tileProvider : {
                    ready : true
                },
                _tileLoadQueueHigh : [],
                _tileLoadQueueMedium : [],
                _tileLoadQueueLow : [],
                _debug : {
                    tilesWaitingForChildren : 0
                }
            },
            terrainProviderChanged : new Event(),
            imageryLayersUpdatedEvent : new Event(),
            beginFrame : function() {},
            update : function() {},
            render : function() {},
            endFrame : function() {}

        };

        scene.globe.getHeight = function() {
            return 0.0;
        };

        scene.globe.destroy = function() {
        };

        scene.globe._surface.updateHeight = function() {
        };

        scene.globe.terrainProviderChanged = new Event();
        defineProperties(scene.globe, {
            terrainProvider : {
                set : function(value) {
                    this.terrainProviderChanged.raiseEvent(value);
                }
            }
        });

        var camera = scene.camera;
        camera.setView({
            destination : Cartesian3.fromDegrees(0.0, 0.0, 10000.0)
        });

        scene.initializeFrame();
        scene.render();

        if (scene.logarithmicDepthBuffer) {
            depth = farDepth = 0.1;
        } else {
            depth = 0.5;
            farDepth = 0.9;
        }
    });

    afterAll(function() {
        scene.destroyForSpecs();
    });

    afterEach(function() {
        cluster = cluster && cluster.destroy();
    });

    it('constructor sets default properties', function() {
        cluster = new EntityCluster();
        expect(cluster.enabled).toEqual(false);
        expect(cluster.pixelRange).toEqual(80);
        expect(cluster.minimumClusterSize).toEqual(2);
        expect(cluster.clusterBillboards).toEqual(true);
        expect(cluster.clusterLabels).toEqual(true);
        expect(cluster.clusterPoints).toEqual(true);

        cluster.enabled = true;
        expect(cluster.enabled).toEqual(true);

        cluster.pixelRange = 30;
        expect(cluster.pixelRange).toEqual(30);

        cluster.minimumClusterSize = 5;
        expect(cluster.minimumClusterSize).toEqual(5);
    });

    it('constructor sets expected properties', function() {
        var options = {
            enabled : true,
            pixelRange : 30,
            minimumClusterSize : 5,
            clusterBillboards : false,
            clusterLabels : false,
            clusterPoints : false
        };
        cluster = new EntityCluster(options);
        expect(cluster.enabled).toEqual(options.enabled);
        expect(cluster.pixelRange).toEqual(options.pixelRange);
        expect(cluster.minimumClusterSize).toEqual(options.minimumClusterSize);
        expect(cluster.clusterBillboards).toEqual(options.clusterBillboards);
        expect(cluster.clusterLabels).toEqual(options.clusterLabels);
        expect(cluster.clusterPoints).toEqual(options.clusterPoints);
    });

    function createBillboardImage() {
        var canvas = document.createElement('canvas');
        canvas.height = canvas.width = 1;

        var context2D = canvas.getContext('2d');
        context2D.clearRect(0, 0, length, length);
        context2D.fillStyle='#FF0000';
        context2D.fillRect(0, 0, length, length);

        return canvas;
    }

    it('clusters billboards', function() {
        cluster = new EntityCluster();
        cluster._initialize(scene);

        var entity = new Entity();
        var billboard = cluster.getBillboard(entity);
        billboard.id = entity;
        billboard.image = createBillboardImage();
        billboard.position = SceneTransforms.drawingBufferToWgs84Coordinates(scene, new Cartesian2(0.0, 0.0), depth);

        entity = new Entity();
        billboard = cluster.getBillboard(entity);
        billboard.id = entity;
        billboard.image = createBillboardImage();
        billboard.position = SceneTransforms.drawingBufferToWgs84Coordinates(scene, new Cartesian2(scene.canvas.clientWidth, scene.canvas.clientHeight), depth);

        var frameState = scene.frameState;
        cluster.update(frameState);

        expect(cluster._clusterLabelCollection).not.toBeDefined();

        cluster.enabled = true;
        cluster.update(frameState);

        expect(cluster._clusterLabelCollection).toBeDefined();
        expect(cluster._clusterLabelCollection.length).toEqual(1);

        cluster.clusterBillboards = false;
        cluster.update(frameState);

        expect(cluster._clusterLabelCollection).not.toBeDefined();
    });

    it('clusters billboards on first update', function() {
        cluster = new EntityCluster();
        cluster._initialize(scene);

        var entity = new Entity();
        var billboard = cluster.getBillboard(entity);
        billboard.id = entity;
        billboard.image = createBillboardImage();
        billboard.position = SceneTransforms.drawingBufferToWgs84Coordinates(scene, new Cartesian2(0.0, 0.0), depth);

        entity = new Entity();
        billboard = cluster.getBillboard(entity);
        billboard.id = entity;
        billboard.image = createBillboardImage();
        billboard.position = SceneTransforms.drawingBufferToWgs84Coordinates(scene, new Cartesian2(scene.canvas.clientWidth, scene.canvas.clientHeight), depth);

        cluster.enabled = true;
        cluster.update(scene.frameState);

        expect(cluster._clusterLabelCollection).toBeDefined();
        expect(cluster._clusterLabelCollection.length).toEqual(1);
    });

    it('clusters labels', function() {
        cluster = new EntityCluster();
        cluster._initialize(scene);

        var entity = new Entity();
        var label = cluster.getLabel(entity);
        label.id = entity;
        label.text = 'a';
        label.position = SceneTransforms.drawingBufferToWgs84Coordinates(scene, new Cartesian2(0.0, 0.0), depth);

        entity = new Entity();
        label = cluster.getLabel(entity);
        label.id = entity;
        label.text = 'b';
        label.position = SceneTransforms.drawingBufferToWgs84Coordinates(scene, new Cartesian2(scene.canvas.clientWidth, scene.canvas.clientHeight), depth);

        var frameState = scene.frameState;
        cluster.update(frameState);

        expect(cluster._clusterLabelCollection).not.toBeDefined();

        cluster.enabled = true;
        cluster.update(frameState);

        expect(cluster._clusterLabelCollection).toBeDefined();
        expect(cluster._clusterLabelCollection.length).toEqual(1);

        cluster.clusterLabels = false;
        cluster.update(frameState);

        expect(cluster._clusterLabelCollection).not.toBeDefined();
    });

    it('clusters labels on first update', function() {
        cluster = new EntityCluster();
        cluster._initialize(scene);

        var entity = new Entity();
        var label = cluster.getLabel(entity);
        label.id = entity;
        label.text = 'a';
        label.position = SceneTransforms.drawingBufferToWgs84Coordinates(scene, new Cartesian2(0.0, 0.0), depth);

        entity = new Entity();
        label = cluster.getLabel(entity);
        label.id = entity;
        label.text = 'b';
        label.position = SceneTransforms.drawingBufferToWgs84Coordinates(scene, new Cartesian2(scene.canvas.clientWidth, scene.canvas.clientHeight), depth);

        cluster.enabled = true;
        cluster.update(scene.frameState);

        expect(cluster._clusterLabelCollection).toBeDefined();
        expect(cluster._clusterLabelCollection.length).toEqual(1);
    });

    it('clusters points', function() {
        cluster = new EntityCluster();
        cluster._initialize(scene);

        var entity = new Entity();
        var point = cluster.getPoint(entity);
        point.id = entity;
        point.pixelSize = 1;
        point.position = SceneTransforms.drawingBufferToWgs84Coordinates(scene, new Cartesian2(0.0, 0.0), depth);

        entity = new Entity();
        point = cluster.getPoint(entity);
        point.id = entity;
        point.pixelSize = 1;
        point.position = SceneTransforms.drawingBufferToWgs84Coordinates(scene, new Cartesian2(scene.canvas.clientWidth, scene.canvas.clientHeight), depth);

        var frameState = scene.frameState;
        cluster.update(frameState);

        expect(cluster._clusterLabelCollection).not.toBeDefined();

        cluster.enabled = true;
        cluster.update(frameState);

        expect(cluster._clusterLabelCollection).toBeDefined();
        expect(cluster._clusterLabelCollection.length).toEqual(1);

        cluster.clusterPoints = false;
        cluster.update(frameState);

        expect(cluster._clusterLabelCollection).not.toBeDefined();
    });

    it('clusters points on first update', function() {
        cluster = new EntityCluster();
        cluster._initialize(scene);

        var entity = new Entity();
        var point = cluster.getPoint(entity);
        point.id = entity;
        point.pixelSize = 1;
        point.position = SceneTransforms.drawingBufferToWgs84Coordinates(scene, new Cartesian2(0.0, 0.0), depth);

        entity = new Entity();
        point = cluster.getPoint(entity);
        point.id = entity;
        point.pixelSize = 1;
        point.position = SceneTransforms.drawingBufferToWgs84Coordinates(scene, new Cartesian2(scene.canvas.clientWidth, scene.canvas.clientHeight), depth);

        cluster.enabled = true;
        cluster.update(scene.frameState);

        expect(cluster._clusterLabelCollection).toBeDefined();
        expect(cluster._clusterLabelCollection.length).toEqual(1);
    });

    it('clusters points that have labels', function() {
        cluster = new EntityCluster();
        cluster._initialize(scene);

        var entity = new Entity();
        var point = cluster.getPoint(entity);
        point.id = entity;
        point.pixelSize = 1;
        point.position = SceneTransforms.drawingBufferToWgs84Coordinates(scene, new Cartesian2(0.0, 0.0), depth);

        entity = new Entity();
        point = cluster.getPoint(entity);
        point.id = entity;
        point.pixelSize = 1;
        point.position = SceneTransforms.drawingBufferToWgs84Coordinates(scene, new Cartesian2(scene.canvas.clientWidth, scene.canvas.clientHeight), depth);

        var frameState = scene.frameState;
        cluster.update(frameState);

        expect(cluster._clusterLabelCollection).not.toBeDefined();

        point.id.label = cluster.getLabel(entity);

        cluster.enabled = true;
        cluster.update(frameState);

        expect(cluster._clusterLabelCollection).toBeDefined();
        expect(cluster._clusterLabelCollection.length).toEqual(1);

        cluster.clusterPoints = false;
        cluster.update(frameState);

        expect(cluster._clusterLabelCollection).not.toBeDefined();
    });

    it('records entity collection indices on getting billboard, label and point', function() {
        cluster = new EntityCluster();
        cluster._initialize(scene);

        var entity = new Entity();
        cluster.getBillboard(entity);
        cluster.getLabel(entity);
        cluster.getPoint(entity);

        expect(cluster._collectionIndicesByEntity[entity.id].billboardIndex).toBeDefined();
        expect(cluster._collectionIndicesByEntity[entity.id].labelIndex).toBeDefined();
        expect(cluster._collectionIndicesByEntity[entity.id].pointIndex).toBeDefined();
    });

    it('removes entity collection indices when billboard, label and point have been removed', function() {
        cluster = new EntityCluster();
        cluster._initialize(scene);

        var entity = new Entity();
        cluster.getBillboard(entity);
        cluster.getLabel(entity);
        cluster.getPoint(entity);

        cluster.removeBillboard(entity);
        cluster.removeLabel(entity);
        cluster.removePoint(entity);

        expect(cluster._collectionIndicesByEntity[entity.id]).toBeUndefined();
    });

    it('can destroy cluster and re-add entities', function() {
        cluster = new EntityCluster();
        cluster._initialize(scene);

        var entity1 = new Entity();
        var billboard = cluster.getBillboard(entity1);
        billboard.id = entity1;

        var entity2 = new Entity();
        var label = cluster.getLabel(entity2);
        label.id = entity2;

        var entity3 = new Entity();
        var point = cluster.getPoint(entity3);
        point.id = entity3;

        cluster.destroy();
        expect(cluster._billboardCollection).not.toBeDefined();
        expect(cluster._labelCollection).not.toBeDefined();
        expect(cluster._pointCollection).not.toBeDefined();

        expect(cluster.getBillboard(entity1)).toBeDefined();
        expect(cluster.getLabel(entity2)).toBeDefined();
        expect(cluster.getPoint(entity3)).toBeDefined();
        expect(cluster._billboardCollection).toBeDefined();
        expect(cluster._labelCollection).toBeDefined();
        expect(cluster._pointCollection).toBeDefined();
    });

    it('does not remove entity collection indices when at least one of billboard, label and point remain', function() {
        cluster = new EntityCluster();
        cluster._initialize(scene);

        var entity = new Entity();
        cluster.getBillboard(entity);
        cluster.getLabel(entity);
        cluster.getPoint(entity);

        cluster.removeBillboard(entity);
        cluster.removeLabel(entity);

        expect(cluster._collectionIndicesByEntity[entity.id]).toBeDefined();
    });

    it('pixel range', function() {
        cluster = new EntityCluster();
        cluster._initialize(scene);

        var entity = new Entity();
        var billboard = cluster.getBillboard(entity);
        billboard.id = entity;
        billboard.image = createBillboardImage();
        billboard.position = SceneTransforms.drawingBufferToWgs84Coordinates(scene, new Cartesian2(0.0, 0.0), depth);

        entity = new Entity();
        billboard = cluster.getBillboard(entity);
        billboard.id = entity;
        billboard.image = createBillboardImage();
        billboard.position = SceneTransforms.drawingBufferToWgs84Coordinates(scene, new Cartesian2(scene.canvas.clientWidth, scene.canvas.clientHeight), depth);

        var frameState = scene.frameState;
        cluster.update(frameState);

        expect(cluster._clusterLabelCollection).not.toBeDefined();

        cluster.enabled = true;
        cluster.update(frameState);

        expect(cluster._clusterLabelCollection).toBeDefined();
        expect(cluster._clusterLabelCollection.length).toEqual(1);

        cluster.pixelRange = 1;
        cluster.update(frameState);

        expect(cluster._clusterLabelCollection).not.toBeDefined();
    });

    it('minimum cluster size', function() {
        cluster = new EntityCluster();
        cluster._initialize(scene);

        var entity = new Entity();
        var billboard = cluster.getBillboard(entity);
        billboard.id = entity;
        billboard.image = createBillboardImage();
        billboard.position = SceneTransforms.drawingBufferToWgs84Coordinates(scene, new Cartesian2(0.0, 0.0), depth);

        entity = new Entity();
        billboard = cluster.getBillboard(entity);
        billboard.id = entity;
        billboard.image = createBillboardImage();
        billboard.position = SceneTransforms.drawingBufferToWgs84Coordinates(scene, new Cartesian2(scene.canvas.clientWidth, 0), depth);

        entity = new Entity();
        billboard = cluster.getBillboard(entity);
        billboard.id = entity;
        billboard.image = createBillboardImage();
        billboard.position = SceneTransforms.drawingBufferToWgs84Coordinates(scene, new Cartesian2(0, scene.canvas.clientHeight), depth);

        entity = new Entity();
        billboard = cluster.getBillboard(entity);
        billboard.id = entity;
        billboard.image = createBillboardImage();
        billboard.position = SceneTransforms.drawingBufferToWgs84Coordinates(scene, new Cartesian2(scene.canvas.clientWidth, scene.canvas.clientHeight), depth);

        var frameState = scene.frameState;
        cluster.update(frameState);

        expect(cluster._clusterLabelCollection).not.toBeDefined();

        cluster.enabled = true;
        cluster.update(frameState);

        expect(cluster._clusterLabelCollection).toBeDefined();
        expect(cluster._clusterLabelCollection.length).toEqual(1);

        cluster.minimumClusterSize = 5;
        cluster.update(frameState);

        expect(cluster._clusterLabelCollection).not.toBeDefined();
    });

    it('clusters around the same point', function() {
        cluster = new EntityCluster();
        cluster._initialize(scene);

        var entity = new Entity();
        var billboard = cluster.getBillboard(entity);
        billboard.id = entity;
        billboard.image = createBillboardImage();
        billboard.position = SceneTransforms.drawingBufferToWgs84Coordinates(scene, new Cartesian2(0.0, 0.0), farDepth);

        entity = new Entity();
        billboard = cluster.getBillboard(entity);
        billboard.id = entity;
        billboard.image = createBillboardImage();
        billboard.position = SceneTransforms.drawingBufferToWgs84Coordinates(scene, new Cartesian2(scene.canvas.clientWidth, scene.canvas.clientHeight), farDepth);

        var frameState = scene.frameState;
        cluster.update(frameState);

        expect(cluster._clusterLabelCollection).not.toBeDefined();

        cluster.enabled = true;
        cluster.update(frameState);

        expect(cluster._clusterLabelCollection).toBeDefined();
        expect(cluster._clusterLabelCollection.length).toEqual(1);

        var position = Cartesian3.clone(cluster._clusterLabelCollection.get(0).position);

        scene.camera.moveForward(1.0e-6);
        cluster.pixelRange = cluster.pixelRange - 1;
        cluster.update(frameState);

        expect(cluster._clusterLabelCollection).toBeDefined();
        expect(cluster._clusterLabelCollection.length).toEqual(1);
        expect(cluster._clusterLabelCollection.get(0).position).toEqual(position);
    });

    it('custom cluster styling', function() {
        cluster = new EntityCluster();
        cluster._initialize(scene);

        cluster.clusterEvent.addEventListener(function(clusteredEntities, cluster) {
            cluster.billboard.show = true;
            cluster.billboard.image = createBillboardImage();
            cluster.label.text = 'cluster';
        });

        var entity = new Entity();
        var point = cluster.getPoint(entity);
        point.id = entity;
        point.pixelSize = 1;
        point.position = SceneTransforms.drawingBufferToWgs84Coordinates(scene, new Cartesian2(0.0, 0.0), farDepth);

        entity = new Entity();
        point = cluster.getPoint(entity);
        point.id = entity;
        point.pixelSize = 1;
        point.position = SceneTransforms.drawingBufferToWgs84Coordinates(scene, new Cartesian2(scene.canvas.clientWidth, scene.canvas.clientHeight), farDepth);

        var frameState = scene.frameState;
        cluster.update(frameState);

        expect(cluster._clusterBillboardCollection).not.toBeDefined();
        expect(cluster._clusterLabelCollection).not.toBeDefined();

        cluster.enabled = true;
        cluster.update(frameState);

        expect(cluster._clusterLabelCollection).toBeDefined();
        expect(cluster._clusterLabelCollection.length).toEqual(1);
        expect(cluster._clusterLabelCollection.get(0).text).toEqual('cluster');

        expect(cluster._clusterBillboardCollection).toBeDefined();
        expect(cluster._clusterBillboardCollection.length).toEqual(1);
    });

    it('renders billboards with invisible labels that are not clustered', function() {
        cluster = new EntityCluster();
        cluster._initialize(scene);
        cluster.minimumClusterSize = 3;

        var dataSource = new CustomDataSource('test');
        dataSource.clustering = cluster;
        dataSource._visualizers = DataSourceDisplay.defaultVisualizersCallback(scene, cluster, dataSource);

        var entityCollection = dataSource.entities;

        entityCollection.add({
            position : SceneTransforms.drawingBufferToWgs84Coordinates(scene, new Cartesian2(0.0, 0.0), depth),
            billboard : {
                image : createBillboardImage()
            },
            label : {
                show : true
            }
        });

        entityCollection.add({
            position : SceneTransforms.drawingBufferToWgs84Coordinates(scene, new Cartesian2(scene.canvas.clientWidth, scene.canvas.clientHeight), depth),
            billboard : {
                image : createBillboardImage()
            },
            label : {
                show : true
            }
        });

        var visualizers = dataSource._visualizers;
        var length = visualizers.length;
        for (var i = 0; i < length; i++) {
            visualizers[i].update(JulianDate.now());
        }

        var frameState = scene.frameState;
        cluster.update(frameState);

        expect(cluster._clusterBillboardCollection).not.toBeDefined();
        expect(cluster._clusterLabelCollection).not.toBeDefined();

        cluster.enabled = true;
        cluster.update(frameState);

        expect(cluster._clusterLabelCollection).not.toBeDefined();
        expect(cluster._clusterBillboardCollection).not.toBeDefined();

        expect(cluster._labelCollection).not.toBeDefined();
        expect(cluster._billboardCollection).toBeDefined();
        expect(cluster._billboardCollection.length).toEqual(2);
    });
}, 'WebGL');
